#!/usr/bin/python3
import os
import subprocess
from progressbar import ProgressBar
from typing import Iterable
import requests
from requests.auth import HTTPBasicAuth
from lxml import etree

PROJECT = "home:gongzt:branches:openEuler:20.03:LTS:Next:Atune"
ACCOUNT = "gongzt"
PASSWORD = "gztGZT@1201"
TMP_FOLDER = "/home/obs"


class OpensuseObsAPI:
    """
    opensuse obs中的相关接口操作
    """

    host = "https://build.openeuler.org/"

    def __init__(self, project=None, account=None, password=None) -> None:
        self._project = project or PROJECT
        self._account = account or ACCOUNT
        self._password = password or PASSWORD
        self.file_size = 1024 * 20

    def _url(self, url, **kwargs):
        return self.host + url.format(**kwargs)

    def _meta(self, package, project):
        meta = """<package name="{package}" project="{project}">
                <title />
                <description />
              </package>""".format(
            package=package, project=project
        )
        return meta

    @property
    def _auth(self):
        return HTTPBasicAuth(self._account, self._password)

    def get_unresolvable(self, project_name=None):
        """
        获取某一个工程下的所有unresolvable的包信息
        """
        if not project_name:
            project_name = self._project
        url = self._url(url="build/{project}/_result", project=project_name)
        response = requests.get(url, auth=self._auth)

        if response.status_code != 200:
            return None
        pkgs = dict()
        etree_element = etree.HTML(response.text)
        unresolvable_result = etree_element.xpath("//result")
        for _unresolvable in unresolvable_result:
            if _unresolvable.xpath("./@repository")[0] != "standard_aarch64":
                continue
            for status in _unresolvable.xpath("./status"):
                if status.xpath("./@code")[0] != "unresolvable":
                    continue
                package = status.xpath("./@package")[0]
                details = status.xpath("./details/text()")[0]
                pkgs[package] = []
                # 提取依赖关系
                for provides in details.split(","):
                    pkgs[package].append(
                        provides.replace("nothing provides", "").strip()
                    )
        return pkgs

    def upload_file(self, package, path, project=None):
        """
        上传文件
        """
        if not project:
            project = self._project
        if os.path.isfile(path):
            files = {path.split("/")[-1]: path}
        else:
            files = {
                file.split("/")[-1]: os.path.join(path, file)
                for file in os.listdir(path)
                if os.path.isfile(os.path.join(path, file))
            }
        progress_bar = ProgressBar().start()
        number = 0
        for file_name, file in files.items():
            url = self._url(
                url="source/{project}/{package}/{filename}",
                project=project,
                package=package,
                filename=file_name,
            )
            try:
                response = requests.put(
                    url,
                    auth=self._auth,
                    data=open(file, "rb"),
                )
                if response.status_code != 200:
                    raise requests.exceptions.HTTPError

            except Exception as error:
                print("上传软件包%s的文件失败:%s \n" % (package, file))
            number += 1
            progress_bar.update(int((number / len(files.keys())) * 100))
        print("\n")

    def create_package(self, package, project=None):
        """
        创建软件包
        """
        if not project:
            project = self._project
        url = self._url(
            url="source/{project}/{package}/_meta", project=project, package=package
        )
        meta = self._meta(package, project).encode("utf-8")
        response = requests.put(
            url,
            auth=self._auth,
            data=meta,
            timeout=15,
        )
        return response.status_code == 200

    def exists_package(self, package, project=None):
        """
        判断指定工程下是否存在特定的软件包
        """
        if not project:
            project = self._project
        url = self._url(
            url="source/{project}/{package}", project=project, package=package
        )
        response = requests.get(url, auth=self._auth)
        if response.status_code == 404:
            return False
        if response.status_code == 200 and package in response.text:
            return True

    def build(self, project=None, package=None):
        """
        触发单个软件包或整个工程的编译
        """
        url = self._url(url="build/{project}?cmd=rebuild", project=project)
        data = None
        if package:
            data = {"package": package}
        response = requests.post(url, auth=self._auth, data=data)
        return response.status_code == 200

    def _project_meta(self, project):
        _meta = """<project name="{project}">
            <title />
            <description />
            <person userid="{account}" role="maintainer" />
            </project>
        """.format(
            project=project, account=self._account
        )
        return _meta

    def create_project(self, project):
        """
        创建工程
        """
        if not project:
            project = self._project
        url = self._url(url="source/{project}/_meta", project=project)
        meta = self._project_meta(project).encode("utf-8")
        response = requests.put(
            url,
            auth=self._auth,
            data=meta,
            timeout=15,
        )
        return response.status_code == 200


class Command:
    """
    dnf 相关命令的执行
    """

    def __init__(self, package, path=None) -> None:
        self.package = package
        self._save_path = path
        if not os.path.exists(self._save_path):
            os.makedirs(self._save_path, exist_ok=True)

    def _download(self):
        cmd_result = subprocess.run(
            args=["dnf", "download", self.package, "--source"],
            shell=False,
            cwd=self._save_path,
        )
        return cmd_result.returncode == 0

    def _unrpm(self, del_source=True):
        cmd_result = subprocess.run(
            args=["unrpm", self.package + "*.src.rpm"], shell=False, cwd=self._save_path
        )
        if del_source and cmd_result.returncode == 0:
            os.system(
                "rm -rf %s" % os.path.join(self._save_path, self.package + "*.src.rpm")
            )
        return os.listdir(self._save_path)

    def osc_upload(self):
        pass

    def get_package(self, del_source=True):
        """
        获取rpm包对应的源码包
        """
        if not self._download():
            raise FileNotFoundError(
                "Failed to download the %s source package ." % self.package
            )
        return self._unrpm(del_source)


class ObsTools:
    """
    obs自动化操作工具
    """

    def __init__(self) -> None:
        self.obs = OpensuseObsAPI()

    def build(self, package, project):
        """obs工程或单个的软件包编译"""
        if self.obs.build(project=project, package=package):
            print("已触发obs的编译操作")
        else:
            print("触发obs软件包编译失败，请稍后重试")

    def depend(self, project, packages=None, file=None):
        """
        解决obs中的unresolvable状态的软件包
        """
        rpms = list()

        # 1. 获取这个工程下所有
        if file and os.path.exists(file):
            # 预留读取excel文件的方法
            pass
        else:
            _packages = self.obs.get_unresolvable(project_name=project)
            if _packages is None:
                print("获取工程下的unresolvable状态的rpm包时，网络发生异常")
                return
            for _, provides in _packages.items():
                rpms.extend(provides)

        if packages and isinstance(packages, Iterable):
            rpms.extend(packages)
        rpms = set(rpms)
        if not rpms:
            print("暂无unresolvable的软件包")
            return

        # 2. 下载每一个软件包并添加上传
        for rpm in rpms:
            path = os.path.join(TMP_FOLDER, rpm)
            files = self._download(package=rpm, path=path)
            if not files:
                print("未查找到%s相关的源码包" % rpm)
                continue
            self.add_package(package=rpm, project=project, path=path)

    def _download(self, package, path):
        cmd = Command(package=package, path=path)
        return cmd.get_package()

    def add_package(self, package, project=None, path=None, build=False):
        """
        新增软件包并上传相关文件
        """
        if not self.obs.exists_package(package, project):
            self.obs.create_package(package=package, project=project)

        if path:
            self.obs.upload_file(package=package, project=project, path=path)

        if build:
            self.build(package=package, project=project)


if __name__ == "__main__":
    tools = OpensuseObsAPI()
    # tools.upload_file(package="python3", path="/home/python3.spec")
    tools.create_project(project="home:gongzt:pp")
